iT邦幫忙

2023 iThome 鐵人賽

DAY 10
0
Vue.js

淺談vue3源碼,很淺的那種系列 第 10

[Day 10] 虛擬dom

  • 分享至 

  • xImage
  •  

「螳螂是一種交配完母方就會把公方吃掉的動物,所以螳螂寶寶意指死了爹的孤兒。」——自尤雨溪望

轉眼間鐵人賽就過去了1/3,我們終於來到了視圖相關的第一章節。

既然要將dom元素放進內存中,我們需要先定義一個能描述dom元素訊息的資料格式。先建立/src/runtime-core/vnode.ts並在裡面寫一個interface吧:

export const Text = Symbol('Text')

export interface VNode {
  type: string | symbol,
  props: Record<string, any>,
  children: Array<VNode | string> | string,
  el: HTMLElement | Text,
  key: string,
  __v_isVnode: true,
  shapeFlag: number;
}

const Text = Symbol('Text')的部分是去年在看vue3源碼的影片時看到的,但今天我去翻真正的源碼時沒找到這個東西,不知道是比較舊的版本的代碼,還是當時看的影片的講師為了方便理解而改寫的。不過Text代表這個虛擬dom是純文字,也挺語意化的,就接著用了。

VNode的type代表這個節點屬於哪種標籤或純文字,例如type可以是h1、div、p等等;props代表這個節點的屬性,它的key可以包含class、id、href等等或任何客製化屬性;children代表這個節點的子節點;el用於存儲這個節點的dom元素(非虛擬dom);key就是v-for的那個key,之後在比對新舊節點時會解釋;__is_Vnode代表這個節點是虛擬dom。

舉例來說,如果在template中有這樣一組標籤:

<div style="color: blueviolet;">
    不曾遇見劉備的<strong>臥龍</strong>,也不過就是諸葛孔明。
</div>

先不考慮最下面的shapeFlag,將其翻譯成虛擬dom,就會是這樣:

{
  type: 'div',
  props: {
    style: 'color: blueviolet;'
  },
  children: [
    '不曾遇見劉備的',
    {
      type: 'strong',
      props: {},
      children: '臥龍',
      __is_vNode: true
    },
    ',也不過就是諸葛孔明。'
  ],
  __v_isVnode: true
};

至於上面沒提到的shapeFlag,用到了一個叫位運算的算法。

簡單來說,當一個對象具備零至多種特徵,例如有一個人既是男人,還是個工程師,我們可以用['男人','工程師']來描述他,但這麼做是比較佔用硬盤或內存空間的。當有許多個對象需要記錄特徵時,我們用2的任意次方對應到一項特徵,把男人看作是1(0001),工程師看作16(1000),這個人的特徵就可以記為17(1001),僅以一個十位數字便能取代一個陣列所佔用的空間。

位運算是一種相對好上手且實用的算法,感興趣的小夥伴不妨可以上網查查,或者參考我以前拍的影片然後順便幫我點一下訂閱。

shapeFlag便是用這種位運算的算法,用最小的內存空間記錄虛擬dom中的每個節點,他們所擁有的特徵。

export const enum ShapeFlags {
  ELEMENT = 1,
  FUNCTIONAL_COMPONENT = 1 << 1,
  STATEFUL_COMPONENT = 1 << 2,
  TEXT_CHILDREN = 1 << 3,
  ARRAY_CHILDREN = 1 << 4,
  SLOTS_CHILDREN = 1 << 5,
  TELEPORT = 1 << 6,
  SUSPENSE = 1 << 7,
  COMPONENT_SHOULD_KEEP_ALIVE = 1 << 8,
  COMPONENT_KEEP_ALIVE = 1 << 9,
  COMPONENT = ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.FUNCTIONAL_COMPONENT
}

<<運算符是用來描述2的次方,1 << x就是二進位的1後面補x個0,也就是十進位的2的x次方。例如:
1 << 1就是二進位的1右邊補一個0,也就是二進位的10,十進位的2。
1 << 2是二進位的1右邊補2個0,也就是二進位的100,十進位的4。
1 << 3是二進位的1000,十進位的8……依此類推。

|運算符同樣用於二進位,a | b回傳a和b作為二進位至少一邊有值的位數。例如:
2 | 4是010和100至少一邊有的位數,也就是110,十進位的6。
10 | 15是1010和1111至少一邊有的位數,也就是1111,十進位的15。
|和<<都可以打開瀏覽器的console,自己試試看喔。

我們可以看到ELEMENT普通的元素是1(1)、TEXT_CHILDREN純文字是8(1000),ARRAY_CHILDREN是16(10000)……而具備STATEFUL_COMPONENT或FUNCTIONAL_COMPONENT任一種特徵的,就被歸類為COMPONENT。

如此一來我們便定義了虛擬dom節點的資料結構,之後我們將定義一個能將虛擬dom渲染成真實dom元素的函數。

githubmain分支commit「[Day 10] 虛擬dom」


上一篇
[Day 09] 視圖
下一篇
[Day 11] runtime-dom——封裝操作dom元素的方法 - 1
系列文
淺談vue3源碼,很淺的那種31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言